private import std.string;
private import std.utf;
private import std.format;
private import std.file;
private import std.path;
private import std.cstream;
private import std.ctype;
private import std.c.stdio;
private import std.math;
private import std.math2;
private import std.random;
private import SDL;
private import SDL_mixer;
private import SDL_ttf;

private:
static int g_width;
static int g_height;
static int g_videoBpp;
static int g_videoFlags;
static SDL_Surface* g_screen; // 実画面
static SDL_Surface* g_buffer; // 仮想画面
static SDL_Surface* [char[]] g_poolBMP;
static Mix_Chunk* [char[]] g_poolChunk;
static Mix_Music* g_music = null;
static SDL_Surface* [int] g_poolFont;
static bool g_quit = false;
static bool g_init = false;
static Uint8 g_pressMouseButtonPrev = 0;
static Uint8 g_pressMouseButton = 0;
static bool[SDLK_LAST] g_pressKeyButtonPrev;
static bool[SDLK_LAST] g_pressKeyButton;
static const char[] g_logname = "run.log";
// 描画オフセット
static int g_offsetX = 0;
static int g_offsetY = 0;
// フォント
static int                g_fontMode;
static TTF_Font* [char[]] g_ttfFont;
static char[]             g_currentFont;
static SDL_Color          g_fontColor;

/**
 * 十字キーのプラス方向の入力判定
 */
static const AXIS_PRESSED_PLUS  =  32768 * 8 / 10;
/**
 * 十字キーのマイナス方向の入力判定
 */
static const AXIS_PRESSED_MINUS = -32768 * 8 / 10;
// フォントモード
enum
{
	FONT_DEFAULT, // デフォルト(たれ文字)
	FONT_TTF_USE, // TTFを使う
};
bool isLeftJ(Sint16 axisX)
{
	return axisX < AXIS_PRESSED_MINUS;
}
bool isUpJ(Sint16 axisY)
{
	return axisY < AXIS_PRESSED_MINUS;
}
bool isRightJ(Sint16 axisX)
{
	return axisX > AXIS_PRESSED_PLUS;
}
bool isDownJ(Sint16 axisY)
{
	return axisY > AXIS_PRESSED_PLUS;
}
static Sint16 g_joyXPrev;
static Sint16 g_joyYPrev;
static Sint16 g_joyX;
static Sint16 g_joyY;
static bool[] g_joyButtonPrev;
static bool[] g_joyButton;
static SDL_Joystick* g_joy;
static int g_joyButtonNum;
static char[] FONT_PATH = "resource/font";
// FPS測定
static int g_fps_cnt;
static int g_fps_view;
static Uint32 g_fps_lastTick;
/**
 * フルスクリーンモード切替
 */
void toggleFullScreen(SDL_Surface* screen)
{
	g_videoFlags ^= SDL_FULLSCREEN; // フルスクリーンフラグ反転
	// スクリーン生成
	screen = SDL_SetVideoMode(g_width, g_height, g_videoBpp, g_videoFlags);
}
/**
 * マウスカーソル表示切替
 */
void toggleShowCursor()
{
	if(SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE) SDL_ShowCursor(SDL_DISABLE);
	else                                        SDL_ShowCursor(SDL_ENABLE);
}

/**
 * フォントの読込
 */
void _loadFont()
{
	g_fontMode     = FONT_DEFAULT;
	char[][] files = listdir(FONT_PATH);
	foreach (char[] fileName; files)
	{
		if(getExt(fileName) != "bmp") continue;
		SDL_Surface* font = SDL_LoadBMP(std.string.toStringz(FONT_PATH ~ "/" ~ fileName));
		if(font == null)
		{
			throw new Error("_loadFont: " ~ std.string.toString(SDL_GetError()));
		}
		// フォント背景色は黒で
		SDL_SetColorKey(font, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(font.format, 0, 0, 0));
		g_poolFont[atoi(getBaseName(fileName))] = font;
	}
}

public:
const static uint HELL_BUTTON_LEFT   = SDL_PRESSED << (SDL_BUTTON_LEFT-1);
const static uint HELL_BUTTON_MIDDLE = SDL_PRESSED << (SDL_BUTTON_MIDDLE-1);
const static uint HELL_BUTTON_RIGHT  = SDL_PRESSED << (SDL_BUTTON_RIGHT-1);
enum {
	HELL_RETURN		= SDLK_RETURN,
	HELL_ESCAPE		= SDLK_ESCAPE,
	HELL_SPACE		= SDLK_SPACE,
	HELL_c			= SDLK_c,
	HELL_x			= SDLK_x,
	HELL_z			= SDLK_z,
	HELL_UP			= SDLK_UP,
	HELL_DOWN		= SDLK_DOWN,
	HELL_RIGHT		= SDLK_RIGHT,
	HELL_LEFT		= SDLK_LEFT,
}
enum {
	HELL_J_UP,
	HELL_J_DOWN,
	HELL_J_RIGHT,
	HELL_J_LEFT,
	HELL_J_MAX,
}

/**
 * SDL初期化
 */
void Hell_init(char[] caption, int width=640, int height=480, bool fullscreen=false)
{
	if(g_init) return;
	g_width      = width;
	g_height     = height;
	g_videoBpp   = 0;
//	g_videoFlags = SDL_SWSURFACE;
	g_videoFlags = SDL_DOUBLEBUF;
	if(fullscreen)
	{
		g_videoFlags |= SDL_FULLSCREEN;
	}
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) < 0)
	{
		throw new Error("Hell_init: Couldn't initialize SDL");
	}
	// スクリーン初期化
	if((g_screen = SDL_SetVideoMode(g_width, g_height, g_videoBpp, g_videoFlags)) == null)
	{
		throw new Error("Hell_init: Couldn't set video mode");
	}
	// キャプション設定
	Hell_setCaption(caption);
	// アイコン設定
	SDL_Surface* icon = SDL_LoadBMP("resource/hell.bmp");
	SDL_WM_SetIcon(icon, null);
	// バックバッファ生成
	g_buffer = SDL_ConvertSurface(g_screen, g_screen.format, g_videoFlags);
	if(!g_buffer)
	{
		throw new Error("Hell_init: Couldn't set video mode");
	}
	// サウンド初期化（音を良くしたい場合、下のコメントを外す）
//	if(Mix_OpenAudio(44100, AUDIO_S16, 2, 8192) < 0)
	if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) < 0)
	{
		throw new Error("Hell_init: " ~ std.string.toString(SDL_GetError()));
	}
	// ログ初期化
	write(g_logname, "");
	if(SDL_NumJoysticks() > 0)
	{
		// ジョイスティック生成
		g_joy = SDL_JoystickOpen(0);
		if(!g_joy)
		{
			throw new Error(std.string.toString(SDL_GetError()));
		}
		g_joyButtonNum = SDL_JoystickNumButtons(g_joy);
		g_joyButtonPrev = new bool[g_joyButtonNum];
		g_joyButton = new bool[g_joyButtonNum];
	}
	else
	{
		g_joy = null;
		g_joyButtonNum = 0;
	}
	// フォントの読込
	_loadFont();
	g_init = true;
}

/**
 * 終了処理
 */
void Hell_quit()
{
	if(g_fontMode == FONT_TTF_USE)
	{
		// TTFを破棄
		foreach (key; g_ttfFont.keys) { _closeFont(g_ttfFont[key]); }
	}
	if(g_joy) SDL_JoystickClose(g_joy);
	Mix_HaltChannel(-1);
	Mix_HaltMusic();
	if(!g_music)
	{
		Mix_FreeMusic(g_music);
	}
	foreach(chunk; g_poolChunk)
	{
		Mix_FreeChunk(chunk);
	}
	Mix_CloseAudio();
	SDL_Quit();
}

/**
 * ログ書き込み
 */
void Hell_write(...)
{
	void _putc(dchar c)
	{
		fputc(c, stdout);
		auto tmp = [c];
		append(g_logname, toUTF8(tmp));
	}
	doFormat(&_putc, _arguments, _argptr);
}
void Hell_write(Exception e)
{
	std.cstream.dout.writeLine(e.toString());
	append(g_logname, e.toString() ~ "\n");
}

/**
 * 画面を更新
 */
void Hell_update()
{
	// 実画面黒塗り
	SDL_Rect rect;
	rect.x = 0; rect.y = 0; rect.w = g_width; rect.h = g_height;
	SDL_FillRect(g_screen, &rect, SDL_MapRGB(g_buffer.format, 0, 0, 0));
	// 仮想画面を実画面に転送
	SDL_Rect dst;
	dst.x = g_offsetX; dst.y = g_offsetY;
	SDL_BlitSurface(g_buffer, cast(SDL_Rect*)0, g_screen, &dst);
	SDL_Flip(g_screen);
//	SDL_UpdateRect(g_screen, 0, 0, 0, 0);
}

/**
 * いったん停止。入力を更新
 */
void Hell_wait(int ms)
{
	// ウエイト（by おめがさん）
	Uint32 nowTick, leftTick;
	static Uint32 prvTick = 0;
	
	if(prvTick == 0) prvTick = SDL_GetTicks();
	
	while(true)
	{
		nowTick  = SDL_GetTicks();
		leftTick = prvTick + ms - nowTick;
		if(leftTick < 1 || leftTick > 9999) break;
		SDL_Delay(1);
	}
	prvTick = nowTick;
	
	// マウス更新
	g_pressMouseButtonPrev = g_pressMouseButton;
	g_pressMouseButton     = SDL_GetMouseState(null, null);

	// キーボード更新
	foreach(i, key; g_pressKeyButton)
	{
		g_pressKeyButtonPrev[i] = key;
	}
	Uint8* keys = SDL_GetKeyState(null);
	for(int i = 0; i < SDLK_LAST; i++)
	{
		g_pressKeyButton[i] = keys[i] == SDL_PRESSED;
	}
	// ジョイスティック更新
	SDL_JoystickUpdate();
	for(int i = 0; i < g_joyButtonNum; i++)
	{
		g_joyButtonPrev[i] = g_joyButton[i];
		g_joyButton[i] = SDL_JoystickGetButton(g_joy, i) == SDL_PRESSED;
	}
	g_joyXPrev = g_joyX;
	g_joyYPrev = g_joyY;
	g_joyX = SDL_JoystickGetAxis(g_joy, 0);
	g_joyY = SDL_JoystickGetAxis(g_joy, 1);

	// イベント更新
	SDL_Event e;
	while ( SDL_PollEvent(&e) ) g_quit = e.type == SDL_QUIT;
	if(Hell_isPushKey(SDLK_F5))      SDL_WM_IconifyWindow();
	if(Hell_isPushKey(SDLK_F9))      toggleFullScreen(g_screen);
	if(Hell_isPushKey(SDLK_F10))     toggleShowCursor();
	if(Hell_isPushKey(SDLK_F12))     SDL_SaveBMP(g_screen, "screenshot.bmp");
	if(Hell_isPressKey(SDLK_ESCAPE)) g_quit = true;
	if(g_quit) throw new Error("dHell system quit.");
	
	// fps計測（by おめがさん）
	g_fps_cnt++;
	if(cast(int)g_fps_lastTick / 1000 != cast(int)nowTick / 1000)
	{
		g_fps_view = g_fps_cnt;
		g_fps_cnt = 0;
		g_fps_lastTick = nowTick;
	}
}

/**
 * キャプション設定（※日本語不可）
 */
void Hell_setCaption(...)
{
	char[] _caption = "";
	void _putc(dchar c)
	{
		auto tmp = [c];
		_caption ~= toUTF8(tmp);
	}
	doFormat(&_putc, _arguments, _argptr);
	SDL_WM_SetCaption(toStringz(_caption), null);
}


/**
 * 描画オフセットの設定
 * @param x オフセット座標(X)
 * @param y オフセット座標(Y)
 */
void Hell_setOffset(int x, int y)
{
	g_offsetX = x;
	g_offsetY = y;
}

version (use_ttf)
{
private:
	/**
	 * WinAPIでシステムディレクトリ取得
	 */
	extern(Windows) export uint GetWindowsDirectoryA(
		char* lpBuffer,
		uint uSize
	);
	/**
	 * フォントのクローズ
	 */
	void _closeFont(TTF_Font* ttfFont)
	{
		if (ttfFont) TTF_CloseFont(ttfFont);
	}
	// TTFフォントの描画
	void _drawFontTTF(char[] msg, int x, int y, int alpha)
	{
		SDL_Surface* surf = TTF_RenderUTF8_Solid(g_ttfFont[g_currentFont], toStringz(msg), g_fontColor);
		SDL_Rect dst;
		dst.x = x;
		dst.y = y;
		SDL_SetAlpha(surf, SDL_SRCALPHA, alpha);
		SDL_BlitSurface(surf, cast(SDL_Rect*)0, g_buffer, &dst);
		SDL_FreeSurface(surf);
	}
public:
	/**
	 * フォントを開く
	 * @param key  フォントのキー
	 * @param size フォントサイズ
	 * @param path フォントのパス
	 */
	void Hell_openFont(char[] key="MSGOTHIC18", int size=18, char[] path="msgothic.ttc")
	{
		if(g_fontMode == FONT_DEFAULT)
		{
			// TTFを使う
			g_fontMode = FONT_TTF_USE;
			if(TTF_Init())
			{
				throw new Error("Hell_openFont: " ~ std.string.toString(SDL_GetError()));
			}
			Hell_setFontColor(0xff, 0xff, 0xff);
		}
		if(key in g_ttfFont) { throw new Error("Hell_openFont: Has exist key: " ~ key); }
		// カレントフォルダにファイルがないか調べる
		// 指定フォントの有無
		char[] path2 = path.dup;
		if(!exists(path2))
		{
			// 存在しない
			auto tmp = new char[256];
			tmp.length = GetWindowsDirectoryA(tmp.ptr, 256);
			// フォルダ取得に失敗..
			if (tmp.length == 0) throw new Error("Hell_openFont : system directory not found.");
			// フォントフォルダを結合してパスを作成
			path2 = tmp ~ "\\fonts\\" ~ path;
			// やっぱりない
			if (!exists(path2)) throw new Error("Hell_openFont : font file not found. " ~ path);
		}
		g_ttfFont[key] = TTF_OpenFont(toStringz(path2), size);
		g_currentFont  = key;
	}
}
else
{
private:
	void _drawFontTTF(char[] msg, int x, int y, int alpha) {}
	void _closeFont(TTF_Font* ttfFont) {}
public:
	void Hell_openFont(char[] key="MSGOTHIC18", char[] path="msgothic.ttc", int size=18) {}
}
/**
 * 使用するフォントを変更します
 * @param key フォントのキー Hell_openFontで指定したもの
 */
void Hell_setFont(char[] key)
{
	if(!(key in g_ttfFont)) { throw new Error("Hell_setFont: Has not exist key: " ~ key); }
	g_currentFont = key;
}
/**
 * 描画フォントの色を変えます(標準フォントの場合は無効)
 */
void Hell_setFontColor(int r, int g, int b)
{
	g_fontColor.r      = r & 0xff;
	g_fontColor.g      = g & 0xff;
	g_fontColor.b      = b & 0xff;
	g_fontColor.unused = 0xff;
}

/**
 * FPSの取得
 */
int Hell_getFPS() { return g_fps_view; }
/**
 * FPSの描画
 * @param x 描画座標(X)
 * @param y 描画座標(Y)
 */
void Hell_drawFPS(int x=-1, int y=-1)
{
	if(x == -1 || y == -1)
	{
		x = 0;
		y = g_height - 28;
	}
	Hell_drawFontEx(x, y, 0xff, "FPS:%d", g_fps_view);
}

/**
 * 点の描画
 * @param x,y   座標
 * @param r,g,b 色
 */
void Hell_drawPoint(int x, int y, int r=0x00, int g=0x00, int b=0x00)
{
	Hell_fillRect(x, y, 1, 1, r, g, b);
}

/**
 * 線分描画
 * @param x1,y1 開始座標
 * @param x2,y2 終了座標
 * @param r,g,b 色
 */
void Hell_drawLine(int x1, int y1, int x2, int y2, int r=0x00, int g=0x00, int b=0x00)
{
	if(x1 == x2 && y1 == y2) return;
	if(x1 == x2)
	{
		// 垂直線
		if(y1 < y2) Hell_fillRect(x1, y1, 1, y2-y1, r, g, b);
		if(y1 > y2) Hell_fillRect(x1, y2, 1, y1-y2, r, g, b);
		return;
	}
	if(y1 == y2)
	{
		// 水平線
		if(x1 < x2) Hell_fillRect(x1, y1, x2-x1, 1, r, g, b);
		if(x1 > x2) Hell_fillRect(x2, y1, x1-x2, 1, r, g, b);
		return;
	}
	
	// Bresenhamで線分描画
	int sx = ( x2 > x1 ) ? 1       : -1;
	int dx = ( x2 > x1 ) ? x2 - x1 : x1 - x2;
	int sy = ( y2 > y1 ) ? 1       : -1;
	int dy = ( y2 > y1 ) ? y2 - y1 : y1 - y2;

	int x = x1;
	int y = y1;

	if( dx >= dy )
	{
		// 傾きが１以下
		int E = -dx;
		for(int i = 0; i <= dx; i++ )
		{
			Hell_drawPoint(x, y, r, g, b);
			x += sx;
			E += 2 * dy;
			if( E >= 0 )
			{
				y += sy;
				E -= 2 * dx;
			}
		}
	}
	else
	{
		// 傾きが１より大きい
		int E = -dy;
		for(int i = 0; i <= dy; i++)
		{
			Hell_drawPoint(x, y, r, g, b);
			y += sy;
			E += 2 * dx;
			if( E >= 0 )
			{
				x += sx;
				E -= 2 * dy;
			}
		}
	}
}

/**
 * 矩形の描画
 * @param x,y 左上座標
 * @param w,h 幅／高さ
 * @param r,g,b 色
 */
void Hell_fillRect(int x=0, int y=0, int w=0, int h=0, int r=0x00, int g=0x00, int b=0x00)
{
	SDL_Rect rect;
	rect.x = x; rect.y = y; rect.w = w; rect.h = h;
	if(w == 0 && h == 0)
	{
		rect.w = g_width;
		rect.h = g_height;
	}
	SDL_FillRect(g_buffer, &rect, SDL_MapRGB(g_buffer.format, r, g, b));
}
// 中心座標を指定して矩形を描画します
void Hell_fillRectEx(int cx=0, int cy=0, int w=0, int h=0, int r=0x00, int g=0x00, int b=0x00)
{
	Hell_fillRect(cx-w, cy-h, w*2, h*2, r, g, b);
}

/**
 * BMP読込み
 * @param key   キー
 * @param path  画像ファイルのパス
 * @param r,g,b 抜き色（-2で抜き色なし。-1で左上の色）
 */
void Hell_loadBMP(char[] key, char[] path, int r=-2, int g=0, int b=0)
{
	if(key in g_poolBMP)
	{
		// 既に存在するので開放する
		SDL_FreeSurface(g_poolBMP[key]);
	}
	SDL_Surface* image = SDL_LoadBMP(std.string.toStringz(path));
	if(image == null)
	{
		throw new Error("Hell_loadBMP: " ~ std.string.toString(SDL_GetError()));
	}
	if(r == -1)
	{
		SDL_PixelFormat* fmt = image.format;
		SDL_LockSurface(image); // ロック
		// 左上ピクセル取得
		Uint32 index = *(cast(Uint32*)image.pixels);
		Uint8 rr, gg, bb;
		SDL_GetRGB(index, fmt, &rr, &gg, &bb);
		SDL_UnlockSurface(image); // アンロック
		SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image.format, rr, gg, bb));
	}
	else if(r >= 0)
	{
		SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image.format, r, g, b));
	}
	g_poolBMP[key] = image;
}

/**
 * BMPをスクリーンに描画
 * @param key     画像のキー
 * @param px, py  描画開始座標（左上）
 */
void Hell_draw(char[] key, int px, int py, int ox=0, int oy=0, int ow=0, int oh=0)
{
	if(!(key in g_poolBMP)) { throw new Error("Hell_draw: Has not exist key: " ~ key); }
	SDL_Surface* surface = g_poolBMP[key];
	SDL_Rect dst;
	dst.x = px;
	dst.y = py;

	SDL_Rect src;
	src.x = ox;
	src.y = oy;
	src.w = surface.w;
	src.h = surface.h;
	if(ow != 0 && oh != 0)
	{
		src.w = ow;
		src.h = oh;
	}
	SDL_BlitSurface(surface, &src, g_buffer, &dst);
	
}
// 中心座標を指定して描画
void Hell_drawEx(char[] key, int cx, int cy, int ox=0, int oy=0, int ow=0, int oh=0)
{
	if(!(key in g_poolBMP)) { throw new Error("Hell_draw: Has not exist key: " ~ key); }
	SDL_Surface* surface = g_poolBMP[key];
	if(ow == 0 || oh == 0)
	{
		ow = surface.w;
		oh = surface.h;
	}
	Hell_draw(key, cx-ow/2, cy-oh/2, ox, oy, ow, oh);
}

/**
 * 画像にアルファ値を設定します
 * @param key   画像のキー
 * @param alpha アルファ値
 */
void Hell_setAlpha(char[] key, int alpha)
{
	if(!(key in g_poolBMP))
	{
		throw new Error("Hell_setAlpha: Has exist key: " ~ key);
	}
	SDL_SetAlpha(g_poolBMP[key], SDL_SRCALPHA, alpha);
}

/**
 * マウス座標Xの取得
 */
int Hell_getMouseX()
{
	int x, y;
	Uint8 button = SDL_GetMouseState(&x, &y);
	return x;
}

/**
 * マウス座標Yの取得
 */
int Hell_getMouseY()
{
	int x, y;
	Uint8 button = SDL_GetMouseState(&x, &y);
	return y;
}

/**
 * マウスを押し続けているかどうか
 */
Uint8 Hell_isPressMouse()
{
	return g_pressMouseButton;
}

/**
 * マウスをその瞬間に押したかどうか
 * <br>
 * Hell_wait()で更新
 */
Uint8 Hell_isPushMouse()
{
	return g_pressMouseButton ^ (g_pressMouseButton & g_pressMouseButtonPrev);
}

/**
 * キーを押し続けているかどうか
 */
bool Hell_isPressKey(int id)
{
	if(id < 0 || SDLK_LAST <= id) return false;
	return g_pressKeyButton[id];
}

/**
 * キーをその瞬間に押したかどうか
 * <br>
 * Hell_wait()で更新
 */
bool Hell_isPushKey(int id)
{
	if(id < 0 || SDLK_LAST <= id) return false;
	return g_pressKeyButton[id] && !g_pressKeyButtonPrev[id];
}


/**
 * SE（WAVファイル）の読み込み
 * @param key  キー
 * @param path サウンドファイルのパス
 */
void Hell_loadWAV(char[] key, char[] path)
{
	if(key in g_poolBMP)
	{
		// キーが存在する場合、破棄する
		Mix_FreeChunk(g_poolChunk[key]);
	}
	Mix_Chunk* chunk = Mix_LoadWAV(std.string.toStringz(path));
	if(!chunk)
	{
		throw new Error("Hell_loadWAV: " ~ std.string.toString(SDL_GetError()) ~ 
                         " (" ~ path ~ ")");
	}
	g_poolChunk[key] = chunk;
}

/**
 * SE（WAVファイル）の再生
 * @param key     キー
 * @param loops   ループ回数（-1で無限ループ）
 * @param channel 再生チャンネル(-1で自動割当)
 */
void Hell_playWAV(char[] key, int loops=0, int channel=-1)
{
	if(!(key in g_poolChunk))
	{
		throw new Error("Hell_playWAV: Has exist key: " ~ key);
	}
	if(channel < -1 || 7 < channel) channel = -1;
	Mix_PlayChannel(channel, g_poolChunk[key], loops);
}
/**
 * SEをワンショットで鳴らす
 * @param key     キー
 * @param channel 使用するチャンネル
 */
void Hell_playWAVEx(char[] key, int channel)
{
	if(channel < -1 || 7 < channel) channel = -1;
	Hell_stopWAV(channel);
	Hell_playWAV(key, 0, channel);
}

/**
 * SE（WAVファイル）を停止
 * @param channel 停止するチャンネル番号(-1で全て停止)
 */
void Hell_stopWAV(int channel=-1)
{
	Mix_HaltChannel(channel);
}

/**
 * ジョイスティックの十字キーを押しているかどうか
 */
bool Hell_isPressJKey(int id)
{
	if(!g_joy) return false;
	if(id < 0 || HELL_J_MAX < id) return false;
	switch(id)
	{
	case HELL_J_UP:
		return isUpJ(g_joyY);
	case HELL_J_DOWN:
		return isDownJ(g_joyY);
	case HELL_J_LEFT:
		return isLeftJ(g_joyX);
	case HELL_J_RIGHT:
		return isRightJ(g_joyX);
	default:
		return false;
	}
}

/**
 * ジョイスティックの十字キーを押したかどうか
 */
bool Hell_isPushJKey(int id)
{
	if(!g_joy) return false;
	if(id < 0 || HELL_J_MAX < id) return false;
	switch(id)
	{
	case HELL_J_UP:
		return isUpJ(g_joyY) && !isUpJ(g_joyYPrev);
	case HELL_J_DOWN:
		return isDownJ(g_joyY) && !isDownJ(g_joyYPrev);
	case HELL_J_LEFT:
		return isLeftJ(g_joyX) && !isLeftJ(g_joyXPrev);
	case HELL_J_RIGHT:
		return isRightJ(g_joyX) && !isRightJ(g_joyXPrev);
	default:
		return false;
	}
}

/**
 * ジョイスティックのボタンを押しているかどうか
 */
bool Hell_isPressJButton(int id)
{
	if(!g_joy) return false;
	if(id < 0 || g_joyButtonNum < id) return false;
	return g_joyButton[id];
}

/**
 * ジョイスティックのボタンを押したかどうか
 * <br>
 * Hell_wait()で更新
 */
bool Hell_isPushJButton(int id)
{
	if(!g_joy) return false;
	if(id < 0 || g_joyButtonNum < id) return false;
	return g_joyButton[id] && !g_joyButtonPrev[id];
}
// キーのユーティリティ
// Z or Return or Space or Joy0 or MouseLEFT
bool Hell_isPressEnter()
{
	if(Hell_isPressKey(HELL_z))                return true;
	if(Hell_isPressKey(HELL_RETURN))           return true;
	if(Hell_isPressKey(HELL_SPACE))            return true;
	if(Hell_isPressJButton(0))                 return true;
	if(Hell_isPressMouse() & HELL_BUTTON_LEFT) return true;
	return false;
}
bool Hell_isPushEnter()
{
	if(Hell_isPushKey(HELL_z))                return true;
	if(Hell_isPushKey(HELL_RETURN))           return true;
	if(Hell_isPushKey(HELL_SPACE))            return true;
	if(Hell_isPushJButton(0))                 return true;
	if(Hell_isPushMouse() & HELL_BUTTON_LEFT) return true;
	return false;
}
// X or Joy1 or MouseRIGHT
bool Hell_isPressCancel()
{
	if(Hell_isPressKey(HELL_x)) return true;
	if(Hell_isPressJButton(1))  return true;
	if(Hell_isPressMouse() & HELL_BUTTON_RIGHT) return true;
	return false;
}
bool Hell_isPushCancel()
{
	if(Hell_isPushKey(HELL_x)) return true;
	if(Hell_isPushJButton(1))  return true;
	if(Hell_isPushMouse() & HELL_BUTTON_RIGHT) return true;
	return false;
}
// C or Joy2 or MouseMIDDLE
bool Hell_isPressMenu()
{
	if(Hell_isPressKey(HELL_c)) return true;
	if(Hell_isPressJButton(2))  return true;
	if(Hell_isPressMouse() & HELL_BUTTON_MIDDLE) return true;
	return false;
}
bool Hell_isPushMenu()
{
	if(Hell_isPushKey(HELL_c)) return true;
	if(Hell_isPushJButton(2))  return true;
	if(Hell_isPushMouse() & HELL_BUTTON_MIDDLE) return true;
	return false;
}

int g_volume = 128;
/**
 * BGM音量の取得
 */
int Hell_getBgmVolume()
{
	return g_volume;
}
/**
 * BGM音量を設定
 * ※Windows2000の場合、音量設定直後にHell_playBgmを呼ぶとフリーズする可能性があります
 * 回避するには、Hell_wait(100)を入れてからBGMの再生を行うようにします
 * @param volume 音量
 */
void Hell_setBgmVolume(int volume=128)
{
	if(volume < 0)   volume = 0;
	if(volume > 128) volume = 128;
	g_volume = volume;
	Mix_VolumeMusic(volume);
}
/**
 * BGMを再生する
 * @param path  ファイルパス
 * @param loops ループ回数（-1で無限ループ）
 * @param ms    フェードイン時間（ms）
 */
void Hell_playBgm(char[] path, int loops=-1, int ms=0)
{
	if(g_music)
	{
		Mix_HaltMusic();
		Mix_FreeMusic(g_music);
	}
	g_music = Mix_LoadMUS(std.string.toStringz(path));
	if(!g_music)
	{
		throw new Error("Hell_playBgm: " ~ std.string.toString(SDL_GetError()));
	}
	if(ms <= 0)
	{
		if(Mix_PlayMusic(g_music, loops) != 0)
		{
			throw new Error("Hell_playBgm: " ~ std.string.toString(SDL_GetError()));
		}
	}
	else
	{
		if(Mix_FadeInMusic(g_music, loops, ms) != 0)
		{
			throw new Error("Hell_playBgm: " ~ std.string.toString(SDL_GetError()));
		}
	}
}

/**
 * BGMを停止する
 * @param ms    フェードアウト時間（ms）
 */
void Hell_stopBgm(int ms)
{
	if(g_music)
	{
		if(ms <= 0)
		{
			Mix_HaltMusic();
			Mix_FreeMusic(g_music);
			g_music = null;
		}
		else
		{
			Mix_FadeOutMusic(ms);
		}
	}
}

/**
 * フォントの描画（標準はasciiのみ）
 * @param msg   ascii文字列
 * @param x,y   描画開始位置
 * @param alpha アルファ値
 */
void Hell_drawFont(char[] msg, int x, int y, int alpha=0xff)
{
	if(g_fontMode == FONT_DEFAULT)
	{
		_drawFontDefault(msg, x, y, alpha);
	}
	else
	{
		_drawFontTTF(msg, x, y, alpha);
	}
}
// フォーマットを指定して描画
void Hell_drawFontEx(int x, int y, int alpha, ...)
{
	char[] _tmpString = "";
	void _putc(dchar c)
	{
		auto tmp = [c];
		_tmpString ~= toUTF8(tmp);
	}
	doFormat(&_putc, _arguments, _argptr);
	Hell_drawFont(_tmpString, x, y, alpha);
}

private:
// 標準フォントの描画
void _drawFontDefault(char[] msg, int x, int y, int alpha)
{
	foreach(c; msg)
	{
		if(!isascii(c)) continue;
		int idx = cast(int)c;
		if(idx == 32)
		{
			x += 12; // スペース
			continue;
		}
		if(idx in g_poolFont)
		{
			SDL_Rect dst;
			dst.x = x;
			dst.y = y;
			SDL_SetAlpha(g_poolFont[idx], SDL_SRCALPHA, alpha);
			SDL_BlitSurface(g_poolFont[idx], cast(SDL_Rect*)0, g_buffer, &dst);
			x += g_poolFont[idx].w;
		}
	}
}
public:

// 数学関係
const float HELL_PI = PI;
float Hell_sin(float rad)               { return sin(rad);             }
float Hell_cos(float rad)               { return cos(rad);             }
float Hell_atan2(float y, float x)      { return atan2(y, x);          }
float Hell_sinEx(float deg)             { return sin(deg2rad(deg));    }
float Hell_cosEx(float deg)             { return cos(deg2rad(deg));    }
float Hell_atan2Ex(float y, float x)    { return rad2deg(atan2(y, x)); }
float Hell_deg2rad(float deg)           { return deg2rad(deg);         }
float Hell_rad2deg(float rad)           { return rad2deg(rad);         }
int   Hell_abs(int a)                   { return std.math.abs(a);      }
float Hell_abs(float a)                 { return std.math.abs(a);      }
float Hell_log10(float a)               { return log10(a);             }
float Hell_distance(float dx, float dy) { return sqrt(dx*dx + dy*dy);  }

// 乱数
/**
 * 乱数の初期化
 * <pre>
 * わざわざ呼ばなくてもプログラム開始時に乱数は初期化されます。
 * 再現性のある乱数を生成したい場合にコールします。
 * </pre>
 * @param seed  種
 * @param index 次の乱数インデックスへの増分
 */
void Hell_randseed(uint seed, uint index)
{
	rand_seed(seed, index);
}
/**
 * rangeより小さいの範囲で乱数を生成
 */
uint Hell_rand(uint range)
{
	return rand()%range;
}
/**
 * begin <= x < endの範囲で乱数を生成
 * @param begin 開始値
 * @param end   終了値
 */
int Hell_randint(int begin, int end)
{
	auto range = end - begin;
	if(range < 1) return -1;
	return begin + rand()%range;
}
/**
 * 配列からランダムで値を取り出します
 */
int Hell_choice(int[] array)
{
	return array[rand()%array.length];
}
/**
 * 配列の値をランダムに入れ替えます
 */
void Hell_shuffle(int[] array)
{
	for(int i = 0; i < array.length; i++)
	{
		int tmp    = array[i];
		int rnd    = Hell_rand(array.length);
		array[i]   = array[rnd];
		array[rnd] = tmp;
	}
}
